﻿#region Usings

using System;
using System.Collections;
using System.Windows.Controls;

using Gonte.Reflection; 

#endregion

namespace Gonte.Wpf.Controls.ViewModel
{
    public class ContentObjectViewModel
        : ContentViewModel
    {
        #region Constants

        private const string NoChildrenProperties = "There are no children properties to select";
        private const string ToManyChildrenProperties = "There are no children properties to select. Use the method that with the property name parameter";

        #endregion

        #region Constructors

        public ContentObjectViewModel(ContentObjectViewModelInfo viewModelInfo)
            : base(viewModelInfo)
        {
        }

        public ContentObjectViewModel(ContentObjectViewModelInfo viewModelInfo, ContentViewModel parent, object model)
            : base(viewModelInfo, parent, model)
        {
            PopulateViewModelFromModel(model);
        }

        #endregion

        #region Methods

        public void AddChild(object child)
        {
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhen(null == viewModelInfo.ChildrenProperties, NoChildrenProperties);
            ExceptionThrower.ThrowWhen(viewModelInfo.ChildrenProperties.Length > 1, ToManyChildrenProperties);

            string childrenProperty = viewModelInfo.ChildrenProperties[0];
            AddChild(childrenProperty, child);
        }

        /// <summary>
        /// Adds a child to the model with more than one children properties
        /// </summary>
        /// <param name="childrenProperty"></param>
        /// <param name="child"></param>
        public void AddChild(string childrenPropertyName, object child)
        {
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhen(null == viewModelInfo.ChildrenProperties, NoChildrenProperties);

            ItemViewModelInfo childViewModelInfo = ContentObjectViewModelInfo.GetInfo(child.GetType().FullName);
            ItemViewModel viewModel = CreateViewModel(childViewModelInfo, child);

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
            object @object = typeAccessor.GetValue(_model, childrenPropertyName);
            IEnumerable collection = @object as IEnumerable;

            if (collection != null) // It is a collection
            {
                if (null != AddItem)
                {
                    AddItem(_model, childrenPropertyName, child);
                }
                else
                {
                    CollectionHelper.AddItem(_model, childrenPropertyName, collection, child);
                }
            }
            else // It is either a null value of a single object. Note: Any existing object will be replaced
            {
                if (null != AddItem)
                {
                    AddItem(_model, childrenPropertyName, child);
                }
                else
                {
                    typeAccessor.SetValue(_model, childrenPropertyName, child);
                }
            }

            if (viewModelInfo.CreateFolders)
            {
                FolderViewModel folderViewModel = FindFolderViewModel(childrenPropertyName);
                folderViewModel.AddChild(viewModel);
            }
            else
            {
                base.AddChild(viewModel);
            }
        }

        public FolderViewModel FindFolderViewModel(string childrenPropertyName)
        {
            foreach(FolderViewModel folderViewModel in Children)
            {
                if (folderViewModel.ChildrenPropertyName.Equals(childrenPropertyName))
                {
                    return folderViewModel;
                }
            }

            return null;
        }

        public bool RemoveChild(object child)
        {
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhen(null == viewModelInfo.ChildrenProperties, NoChildrenProperties);
            ExceptionThrower.ThrowWhen(viewModelInfo.ChildrenProperties.Length > 1, ToManyChildrenProperties);

            string childrenProperty = viewModelInfo.ChildrenProperties[0];
            return RemoveChild(childrenProperty, child);
        }

        public bool RemoveChild(string childrenProperty, object child)
        {
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhen(null == viewModelInfo.ChildrenProperties, NoChildrenProperties);

            ItemViewModel viewModel = FindChild(child);

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
            object @object = typeAccessor.GetValue(_model, childrenProperty);
            IEnumerable collection = @object as IEnumerable;

            if (collection != null) // It is a collection
            {
                CollectionHelper.RemoveItem(_model, childrenProperty, collection, child);
            }
            else // It is either a null value of a single object. Note: Any existing object will be set to null
            {
                typeAccessor.SetValue(_model, childrenProperty, null);
            }

            return base.RemoveChild(viewModel);
        }

        public bool MoveChildUp(object child)
        {
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhen(null == viewModelInfo.ChildrenProperties, NoChildrenProperties);
            ExceptionThrower.ThrowWhen(viewModelInfo.ChildrenProperties.Length > 1, ToManyChildrenProperties);

            string childrenProperty = viewModelInfo.ChildrenProperties[0];
            return MoveChildUp(childrenProperty, child);
        }

        private bool MoveChildUp(string childrenProperty, object child)
        {
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhen(null == viewModelInfo.ChildrenProperties, NoChildrenProperties);

            ItemViewModel viewModel = FindChild(child);

            int newIndex;
            if (!base.MoveChildUp(viewModel, out newIndex))
            {
                return false;
            }

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
            object @object = typeAccessor.GetValue(_model, childrenProperty);
            IEnumerable collection = @object as IEnumerable;

            if (collection != null) // It is a collection
            {
                CollectionHelper.SwapItems(collection, newIndex + 1, newIndex);
                return true;
            }

            //It is either a null value or a single object. Nothing to move
            return false;
        }

        public bool MoveChildDown(object child)
        {
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhen(null == viewModelInfo.ChildrenProperties, NoChildrenProperties);
            ExceptionThrower.ThrowWhen(viewModelInfo.ChildrenProperties.Length > 1, ToManyChildrenProperties);

            string childrenProperty = viewModelInfo.ChildrenProperties[0];
            return MoveChildDown(childrenProperty, child);
        }

        public bool MoveChildDown(string childrenProperty, object child)
        {
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhen(null == viewModelInfo.ChildrenProperties, NoChildrenProperties);

            ItemViewModel viewModel = FindChild(child);

            int newIndex;
            if (!base.MoveChildDown(viewModel, out newIndex))
            {
                return false;
            }

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
            object @object = typeAccessor.GetValue(_model, childrenProperty);
            IEnumerable collection = @object as IEnumerable;

            if (collection != null) // It is a collection
            {
                CollectionHelper.SwapItems(collection, newIndex - 1, newIndex);
                return true;
            }

            //It is either a null value or a single object. Nothing to move
            return false;
        }

        #endregion

        #region Properties

        /// <summary>
        /// The model this view model maps to
        /// </summary>
        public new object Model
        {
            get { return _model; }

            set
            {
                PopulateViewModelFromModel(value);
            }
        }

        #endregion

        #region Overridables

        public override void LoadChildren()
        {
            ContentObjectViewModelInfo objectViewModelInfo = _viewModelInfo as ContentObjectViewModelInfo;

            if (null == objectViewModelInfo)
            {
                return;
            }

            Children.Clear();

            foreach (string childrenPropertyName in objectViewModelInfo.ChildrenProperties)
            {
                TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
                object child = typeAccessor.GetValue(_model, childrenPropertyName);
                Type type = CollectionHelper.GetItemType(child.GetType());
                ItemViewModelInfo childViewModelInfo = ItemObjectViewModelInfo.GetInfo(type);

                if (objectViewModelInfo.CreateFolders)
                {
                    ContentViewModelInfo folderViewModelInfo = objectViewModelInfo.GetFolderViewModelInfo(childrenPropertyName);
                    FolderViewModel folderViewModel = new FolderViewModel(folderViewModelInfo, childrenPropertyName, this, _model);
                    CreateChildren(folderViewModel, childViewModelInfo, child);
                    Children.Add(folderViewModel);
                }
                else
                {
                    CreateChildren(this, childViewModelInfo, child);
                }
            }
        }

        #endregion

        #region Helpers

        private void PopulateViewModelFromModel(object model)
        {
            _model = model;
            Type type = TypeHelper.UnwrapType(_model.GetType());
            ContentObjectViewModelInfo viewModelInfo = (ContentObjectViewModelInfo)_viewModelInfo;

            ExceptionThrower.ThrowWhenNot(type == viewModelInfo.Type, "Types must match");

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(type);

            // Set the label
            _label = (string)typeAccessor.GetValue(_model, viewModelInfo.LabelProperty);

            UpdateIconSource();

            if (viewModelInfo.LoadOnDemand)
            {
                PrepareLoadOnDemand();
            }
            else
            {
                LoadChildren();
            }
        }

        private void PrepareLoadOnDemand()
        {
            ContentObjectViewModelInfo viewModelInfo = _viewModelInfo as ContentObjectViewModelInfo;

            if (null == viewModelInfo)
            {
                return;
            }

            Children.Clear();

            foreach (string childrenPropertyName in viewModelInfo.ChildrenProperties)
            {
                if (viewModelInfo.CreateFolders)
                {
                    ContentViewModelInfo folderViewModelInfo = viewModelInfo.GetFolderViewModelInfo(childrenPropertyName);
                    FolderViewModel folderViewModel = new FolderViewModel(folderViewModelInfo, childrenPropertyName, this, _model);
                    folderViewModel.Children.Add(LoadOnDemandViewModel.Instance);
                    Children.Add(folderViewModel);
                }
                else
                {
                    Children.Add(LoadOnDemandViewModel.Instance);
                }
            }
        }

        internal void CreateChildren(ContentViewModel contentViewModel, ItemViewModelInfo childViewModelInfo, object child)
        {
            ContentViewModelInfo contentViewModelInfo = (ContentViewModelInfo)_viewModelInfo;
            IEnumerable collection = child as IEnumerable;

            if (collection != null) // It is a collection
            {
                IEnumerable items = CollectionHelper.GetItemValues(collection);

                foreach (object item in items)
                {
                    // Filter the children
                    if ((null == contentViewModelInfo.AllowChild)
                        || (null != contentViewModelInfo.AllowChild
                        && contentViewModelInfo.AllowChild(item)))
                    {
                        ItemViewModel viewModel = CreateViewModel(childViewModelInfo, item);
                        contentViewModel.Children.Add(viewModel);
                    }
                }

                contentViewModel.SortChildren();
            }
            else // It is either a null value of a single object
            {
                if ((null == contentViewModelInfo.AllowChild)
                        || (null != contentViewModelInfo.AllowChild
                        && contentViewModelInfo.AllowChild(child)))
                {
                    ItemViewModel viewModel = CreateViewModel(childViewModelInfo, child);
                    contentViewModel.Children.Add(viewModel);
                }
            }
        }

        private ItemViewModel CreateViewModel(ItemViewModelInfo childViewModelInfo, object item)
        {
            ContentObjectViewModelInfo viewModelInfo = childViewModelInfo as ContentObjectViewModelInfo;

            if (null != viewModelInfo)
            {
                return new ContentObjectViewModel(viewModelInfo, this, item);
            }

            return new ItemObjectViewModel((ItemObjectViewModelInfo)childViewModelInfo, this, item);
        }  

        #endregion

        #region Adding a child to the model event handler

        public delegate void AddItemHandler(object model, string childrenPropertyName, object child);

        public AddItemHandler AddItem; 

        #endregion
    }
}
